
/**
 * AppleIIGo
 * Floppy Disk Image class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include <string.h>
#include "fileutilities.h"
#include "GCR62DiskImage.h"
#include "GCR53DiskImage.h"
#include "GCRDisk35DiskImage.h"
#include "NIBDiskImage.h"
#include "V2DDiskImage.h"
#include "TwoImgDiskImage.h"
#include "FDIDiskImage.h"

FloppyDiskImage * FloppyDiskImage::getInstance(UINT32 diskType,
											   char * diskImagePath) {
	if (!diskImagePath)
		return new FloppyDiskImage(diskType, 0);
	
	char * extension = getFileExtension(diskImagePath);
	UINT32 offset = 0;
	UINT32 byteNum = getFileByteNum(diskImagePath);
	
	UINT8 gcrVolume = 254;
	bool isWriteProtectionForced = false;
	bool isEncapsulated;
	
	do {
		isEncapsulated = false;
		
		if (!strcasecmp(extension, "dsk") ||
			!strcasecmp(extension, "do") ||
			!strcasecmp(extension, "d13")) {
			if (GCR62DiskImage::isDiskImageValid(diskType,
												 byteNum))
				return new GCR62DiskImage(diskImagePath,
										  offset,
										  byteNum,
										  GCR62_SECTORORDER_DOS,
										  gcrVolume,
										  isWriteProtectionForced);
			if (GCR53DiskImage::isDiskImageValid(diskType,
												 byteNum))
				return new GCR53DiskImage(diskImagePath,
										  offset,
										  byteNum,
										  gcrVolume,
										  isWriteProtectionForced);
		} else if (!strcasecmp(extension, "po") ||
				   !strcasecmp(extension, "vdsk") ||
				   !strcasecmp(extension, "hdv")) {
			if (GCR62DiskImage::isDiskImageValid(diskType,
												 byteNum))
				return new GCR62DiskImage(diskImagePath,
										  offset,
										  byteNum,
										  GCR62_SECTORORDER_PRODOS,
										  gcrVolume,
										  isWriteProtectionForced);
			if (GCRDisk35DiskImage::isDiskImageValid(diskType,
													 byteNum))
				return new GCRDisk35DiskImage(diskImagePath,
											  offset,
											  byteNum,
											  isWriteProtectionForced);
		} else if (!strcasecmp(extension, "img")) {
			if (GCR62DiskImage::isDiskImageValid(diskType,
												 byteNum))
				return new GCR62DiskImage(diskImagePath,
										  offset,
										  byteNum,
										  GCR62_SECTORORDER_IMG,
										  gcrVolume,
										  isWriteProtectionForced);
		} else if (!strcasecmp(extension, "cpm")) {
			if (GCR62DiskImage::isDiskImageValid(diskType,
												 byteNum))
				return new GCR62DiskImage(diskImagePath,
										  offset,
										  byteNum,
										  GCR62_SECTORORDER_CPM,
										  gcrVolume,
										  isWriteProtectionForced);
		} else if (!strcasecmp(extension, "nib")) {
			if (NIBDiskImage::isDiskImageValid(diskType,
											   byteNum))
				return new NIBDiskImage(diskImagePath,
										offset,
										byteNum,
										isWriteProtectionForced);
		} else if (!strcasecmp(extension, "v2d")) {
			if (V2DDiskImage::isDiskImageValid(diskType,
											   diskImagePath))
				return new V2DDiskImage(diskImagePath);
		} else if (!strcasecmp(extension, "2img") ||
				   !strcasecmp(extension, "2mg")) {
			if (TwoImgDiskImage::isDiskImageValid(diskImagePath,
												  &offset,
												  &byteNum,
												  &extension,
												  &gcrVolume,
												  &isWriteProtectionForced))
				isEncapsulated = true;
		} else if (!strcasecmp(extension, "fdi")) {
			if (FDIDiskImage::isDiskImageValid(diskType, diskImagePath))
				return new FDIDiskImage(diskType, diskImagePath);
		}
	} while (isEncapsulated);
	
	return 0;
}

FloppyDiskImage::FloppyDiskImage(UINT32 diskType, char * diskImagePath) {
	this->diskImagePath = 0;
	
	this->diskType = diskType;
	switch (diskType) {
		case FLOPPYDISKTYPE_5_25:
		default:
			diskTrackNum = DISK_5_25_TRACKNUM;
			diskHeadNum = DISK_5_25_HEADNUM;
			break;
		case FLOPPYDISKTYPE_3_5:
			diskTrackNum = DISK_3_5_TRACKNUM;
			diskHeadNum = DISK_3_5_HEADNUM;
			break;
	}
	diskTrackIndex = 0;
	diskHeadIndex = 0;
	diskTrackBitNum = new UINT32[diskTrackNum * diskHeadNum];
	diskTrackOffset = new UINT32[diskTrackNum * diskHeadNum];
	diskData = 0;
	emptyDiskData = 0;
	
	if (!diskTrackBitNum || !diskTrackOffset)
		return;
	
	for (UINT32 trackIndex = 0; trackIndex < diskTrackNum; trackIndex++)
		for (UINT32 headIndex = 0; headIndex < diskHeadNum; headIndex++) {
			setTrackIndex(trackIndex);
			setHeadIndex(headIndex);
			setTrackBitNum(getTrackBitNum(trackIndex, diskType));
		}
	
	trackBitIndex = 0;
	updateTrack();
	
	isWriteProtectedValue = false;
	isWriteEnabledValue = false;
	isModifiedValue = false;
	
	if (!diskImagePath)
		return;
	
	if (!(diskImageFile = fopen(diskImagePath, "r+b"))) {
		if (!(diskImageFile = fopen(diskImagePath, "rb")))
			return;
		isWriteProtectedValue = true;
	} else
		isWriteProtectedValue = false;

	this->diskImagePath = diskImagePath ? strdup(diskImagePath) : 0;
}

FloppyDiskImage::~FloppyDiskImage() {
	if (diskImageFile)
		fclose(diskImageFile);
	if (diskImagePath)
		delete diskImagePath;
	
	if (diskData)
		delete diskData;
	if (diskTrackOffset)
		delete diskTrackOffset;
	if (diskTrackBitNum)
		delete diskTrackBitNum;
}

UINT32 FloppyDiskImage::getTrackBitNum(UINT32 trackIndex, UINT32 diskType) {
	if (diskType == FLOPPYDISKTYPE_5_25)
		return DISK_5_25_TRACK_BITNUM;
	
	UINT32 disk35BitNum[] = {
		DISK_3_5_DATARATE * 60 / DISK_3_5_MOTORRPM_1,
		DISK_3_5_DATARATE * 60 / DISK_3_5_MOTORRPM_2,
		DISK_3_5_DATARATE * 60 / DISK_3_5_MOTORRPM_3,
		DISK_3_5_DATARATE * 60 / DISK_3_5_MOTORRPM_4,
		DISK_3_5_DATARATE * 60 / DISK_3_5_MOTORRPM_5,
	};

	return disk35BitNum[trackIndex >> 4];
}

void FloppyDiskImage::setTrackBitNum(UINT32 bitNum) {
	if (diskTrackBitNum)
		diskTrackBitNum[diskHeadNum * diskTrackIndex + diskHeadIndex] = bitNum;
}

bool FloppyDiskImage::allocateDisk() {
	UINT32 dataOffset = 0;
	for (UINT32 trackIndex = 0; trackIndex < diskTrackNum; trackIndex++)
		for (UINT32 headIndex = 0; headIndex < diskHeadNum; headIndex++) {
			UINT32 index = diskHeadNum * trackIndex + headIndex;
			diskTrackOffset[index] = dataOffset;
			dataOffset += (diskTrackBitNum[index] + 7) / 8;
		}
	
	diskData = new UINT8[dataOffset];
 	if (diskData)
		memset(diskData, 0, dataOffset);
	
	return (diskData != 0);
}

void FloppyDiskImage::updateTrack() {
	if (diskData) {
		UINT32 index = diskHeadNum * diskTrackIndex + diskHeadIndex;
		trackData = &diskData[diskTrackOffset[index]];
		trackBitNum = diskTrackBitNum[index];
		trackBitIndex %= trackBitNum;
	} else {
		trackData = &emptyDiskData;
		trackBitNum = 8;
		trackBitIndex = 0;
	}
}

UINT32 FloppyDiskImage::getDiskType() {
	return diskType;
}

UINT32 FloppyDiskImage::getTrackNum() {
	return diskTrackNum;
}

void FloppyDiskImage::setTrackIndex(UINT32 value) {
	if (value >= diskTrackNum)
		value = diskTrackNum - 1;
	
	diskTrackIndex = value;
	
	updateTrack();
}

UINT32 FloppyDiskImage::getTrackIndex() {
	return diskTrackIndex;
}

UINT32 FloppyDiskImage::getHeadNum() {
	return diskHeadNum;
}

void FloppyDiskImage::setHeadIndex(UINT32 value) {
	if (value >= diskHeadNum)
		value = diskHeadNum - 1;
	
	diskHeadIndex = value;
	
	updateTrack();
}

UINT32 FloppyDiskImage::getHeadIndex() {
	return diskHeadIndex;
}

void FloppyDiskImage::getTrackData(UINT8 ** data,
								   UINT32 * bitNum,
								   UINT32 * bitIndex) {
	*data = trackData;
	*bitNum = trackBitNum;
	*bitIndex = *bitIndex % trackBitNum;
}

UINT8 FloppyDiskImage::readNibble() {
	UINT32 bitNum;
	return readNibble(&bitNum);
}

UINT8 FloppyDiskImage::readNibble(UINT32 * bitNum) {
	UINT8 value = 0;
	
	for (*bitNum = 0; (*bitNum < trackBitNum) && !(value & 0x80); (*bitNum)++) {
		UINT32 byteIndex = trackBitIndex >> 3;
		UINT32 bitIndex = (~trackBitIndex) & 0x7;
		value <<= 1;
		value |= (trackData[byteIndex] >> bitIndex) & 1;
		
		trackBitIndex++;
		if (trackBitIndex >= trackBitNum)
			trackBitIndex = 0;
	}
	
	return value;
}

void FloppyDiskImage::writeNibble(UINT8 value) {
	writeNibble(value, 8);
}

void FloppyDiskImage::writeNibble(UINT8 value, UINT32 bitNum) {
	while (bitNum--) {
		UINT32 byteIndex = trackBitIndex >> 3;
		UINT32 bitIndex = (~trackBitIndex) & 0x7;
		bool bit = (value & 0x80) >> 7;
		value <<= 1;
		
		trackData[byteIndex] &= ~(1 << bitIndex);
		trackData[byteIndex] |= (bit << bitIndex);
		
		trackBitIndex++;
		if (trackBitIndex >= trackBitNum)
			trackBitIndex = 0;
	}
}

UINT32 FloppyDiskImage::getNibbleNum() {
	UINT32 bitNum;
	for (UINT32 bitCount = 0; bitCount < trackBitNum; bitCount += bitNum)
		readNibble(&bitNum);
	
	UINT32 nibbleCount = 0;
	for (UINT32 bitCount = 0; bitCount < trackBitNum; bitCount += bitNum)
		if (readNibble(&bitNum))
			nibbleCount++;
	
	return nibbleCount;
}

void FloppyDiskImage::setWriteProtected(bool isWriteProtected) {
	isWriteProtectedValue = isWriteProtected;
	isWriteEnabledValue = !isWriteProtected;
}

bool FloppyDiskImage::isWriteProtected() {
	return isWriteProtectedValue;
}

bool FloppyDiskImage::isWriteEnabled() {
	return isWriteEnabledValue;
}

void FloppyDiskImage::setModified() {
	isModifiedValue = true;
}

bool FloppyDiskImage::isModified() {
	return isModifiedValue;
}

bool FloppyDiskImage::isDiskImageOpened() {
	return (diskImageFile != 0);
}

char * FloppyDiskImage::getDiskImagePath() {
	return diskImagePath;
}
